{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "import jieba\n",
    "from libsvm import svm\n",
    "from libsvm.svmutil import svm_read_problem,svm_train,svm_predict,svm_save_model,svm_load_model\n",
    "\n",
    "## 数据集下载：https://pan.baidu.com/s/1ZkxGIvvGml3vig-9_s1pRw\n",
    "## 百度网盘下载加速：https://www.baiduwp.com/?m=index\n",
    "news_file='cnews.train.txt'         ##原始是数据\n",
    "test_file='cnews.test.txt'          ##测试数据\n",
    "output_word_file='cnews_dict.txt'   ##进过分词后的数\n",
    "output_word_test_file='cnews_dict_test.txt'\n",
    "feature_file='cnews_feature_file.txt'             ##最后生成的词向量文件\n",
    "feature_test_file='cnews_feature_test_file.txt'\n",
    "model_filename='cnews_model'                     ##模型保存的文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "马晓旭意外受伤让国奥警惕 无奈大雨格外青睐殷家军记者傅亚雨沈阳报道 来到沈阳，国奥队依然没有摆脱雨水的困扰。7月31日下午6点，国奥队的日常训练再度受到大雨的干扰，无奈之下队员们只慢跑了25分钟就草草收场。31日上午10点，国奥队在奥体中心外场训练的时候，天就是阴沉沉的，气象预报显示当天下午沈阳就有大雨，但幸好队伍上午的训练并没有受到任何干扰。下午6点，当球队抵达训练场时，大雨已经下了几个小时，而且丝毫没有停下来的意思。抱着试一试的态度，球队开始了当天下午的例行训练，25分钟过去了，天气没有任何转好的迹象，为了保护球员们，国奥队决定中止当天的训练，全队立即返回酒店。在雨中训练对足球队来说并不是什么稀罕事，但在奥运会即将开始之前，全队变得“娇贵”了。在沈阳最后一周的训练，国奥队首先要保证现有的球员不再出现意外的伤病情况以免影响正式比赛，因此这一阶段控制训练受伤、控制感冒等疾病的出现被队伍放在了相当重要的位置。而抵达沈阳之后，中后卫冯萧霆就一直没有训练，冯萧霆是7月27日在长春患上了感冒，因此也没有参加29日跟塞尔维亚的热身赛。队伍介绍说，冯萧霆并没有出现发烧症状，但为了安全起见，这两天还是让他静养休息，等感冒彻底好了之后再恢复训练。由于有了冯萧霆这个例子，因此国奥队对雨中训练就显得特别谨慎，主要是担心球员们受凉而引发感冒，造成非战斗减员。而女足队员马晓旭在热身赛中受伤导致无缘奥运的前科，也让在沈阳的国奥队现在格外警惕，“训练中不断嘱咐队员们要注意动作，我们可不能再出这样的事情了。”一位工作人员表示。从长春到沈阳，雨水一路伴随着国奥队，“也邪了，我们走到哪儿雨就下到哪儿，在长春几次训练都被大雨给搅和了，没想到来沈阳又碰到这种事情。”一位国奥球员也对雨水的“青睐”有些不解。\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Dumping model to file cache /var/folders/jr/sc47kr2548z426dnw3xkrjqm0000gn/T/jieba.cache\n",
      "Loading model cost 1.041 seconds.\n",
      "Prefix dict has been built succesfully.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "马晓旭/ 意外/ 受伤/ 让/ 国奥/ 警惕/  / 无奈/ 大雨/ 格外/ 青睐/ 殷家/ 军/ 记者/ 傅亚雨/ 沈阳/ 报道/  / 来到/ 沈阳/ ，/ 国奥队/ 依然/ 没有/ 摆脱/ 雨水/ 的/ 困扰/ 。/ 7/ 月/ 31/ 日/ 下午/ 6/ 点/ ，/ 国奥队/ 的/ 日常/ 训练/ 再度/ 受到/ 大雨/ 的/ 干扰/ ，/ 无奈/ 之下/ 队员/ 们/ 只/ 慢跑/ 了/ 25/ 分钟/ 就/ 草草收场/ 。/ 31/ 日/ 上午/ 10/ 点/ ，/ 国奥队/ 在/ 奥体中心/ 外场/ 训练/ 的/ 时候/ ，/ 天/ 就是/ 阴沉沉/ 的/ ，/ 气象预报/ 显示/ 当天/ 下午/ 沈阳/ 就/ 有/ 大雨/ ，/ 但/ 幸好/ 队伍/ 上午/ 的/ 训练/ 并/ 没有/ 受到/ 任何/ 干扰/ 。/ 下午/ 6/ 点/ ，/ 当/ 球队/ 抵达/ 训练场/ 时/ ，/ 大雨/ 已经/ 下/ 了/ 几个/ 小时/ ，/ 而且/ 丝毫/ 没有/ 停下来/ 的/ 意思/ 。/ 抱/ 着/ 试一试/ 的/ 态度/ ，/ 球队/ 开始/ 了/ 当天/ 下午/ 的/ 例行/ 训练/ ，/ 25/ 分钟/ 过去/ 了/ ，/ 天气/ 没有/ 任何/ 转好/ 的/ 迹象/ ，/ 为了/ 保护/ 球员/ 们/ ，/ 国奥队/ 决定/ 中止/ 当天/ 的/ 训练/ ，/ 全队/ 立即/ 返回/ 酒店/ 。/ 在/ 雨/ 中/ 训练/ 对/ 足球队/ 来说/ 并/ 不是/ 什么/ 稀罕/ 事/ ，/ 但/ 在/ 奥运会/ 即将/ 开始/ 之前/ ，/ 全队/ 变得/ “/ 娇贵/ ”/ 了/ 。/ 在/ 沈阳/ 最后/ 一周/ 的/ 训练/ ，/ 国奥队/ 首先/ 要/ 保证/ 现有/ 的/ 球员/ 不再/ 出现意外/ 的/ 伤病/ 情况/ 以免/ 影响/ 正式/ 比赛/ ，/ 因此/ 这一/ 阶段/ 控制/ 训练/ 受伤/ 、/ 控制/ 感冒/ 等/ 疾病/ 的/ 出现/ 被/ 队伍/ 放在/ 了/ 相当/ 重要/ 的/ 位置/ 。/ 而/ 抵达/ 沈阳/ 之后/ ，/ 中/ 后卫/ 冯萧霆/ 就/ 一直/ 没有/ 训练/ ，/ 冯萧霆/ 是/ 7/ 月/ 27/ 日/ 在/ 长春/ 患上/ 了/ 感冒/ ，/ 因此/ 也/ 没有/ 参加/ 29/ 日/ 跟/ 塞尔维亚/ 的/ 热身赛/ 。/ 队伍/ 介绍/ 说/ ，/ 冯萧霆/ 并/ 没有/ 出现/ 发烧/ 症状/ ，/ 但/ 为了/ 安全/ 起/ 见/ ，/ 这/ 两天/ 还是/ 让/ 他/ 静养/ 休息/ ，/ 等/ 感冒/ 彻底/ 好/ 了/ 之后/ 再/ 恢复/ 训练/ 。/ 由于/ 有/ 了/ 冯萧霆/ 这个/ 例子/ ，/ 因此/ 国奥队/ 对雨中/ 训练/ 就/ 显得/ 特别/ 谨慎/ ，/ 主要/ 是/ 担心/ 球员/ 们/ 受凉/ 而/ 引发/ 感冒/ ，/ 造成/ 非战斗/ 减员/ 。/ 而/ 女足/ 队员/ 马晓旭/ 在/ 热身赛/ 中/ 受伤/ 导致/ 无缘/ 奥运/ 的/ 前科/ ，/ 也/ 让/ 在/ 沈阳/ 的/ 国奥队/ 现在/ 格外/ 警惕/ ，/ “/ 训练/ 中/ 不断/ 嘱咐/ 队员/ 们/ 要/ 注意/ 动作/ ，/ 我们/ 可/ 不能/ 再出/ 这样/ 的/ 事情/ 了/ 。/ ”/ 一位/ 工作人员/ 表示/ 。/ 从/ 长春/ 到/ 沈阳/ ，/ 雨水/ 一路/ 伴随/ 着/ 国奥队/ ，/ “/ 也/ 邪/ 了/ ，/ 我们/ 走/ 到/ 哪儿/ 雨/ 就/ 下/ 到/ 哪儿/ ，/ 在/ 长春/ 几次/ 训练/ 都/ 被/ 大雨/ 给/ 搅和/ 了/ ，/ 没想到/ 来/ 沈阳/ 又/ 碰到/ 这种/ 事情/ 。/ ”/ 一位/ 国奥/ 球员/ 也/ 对/ 雨水/ 的/ “/ 青睐/ ”/ 有些/ 不解/ 。\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "with open(news_file, 'r') as f:       ##读取新闻文章\n",
    "    lines = f.readlines()\n",
    "\n",
    "label, content = lines[0].strip('\\r\\n').split('\\t')\n",
    "print(content)\n",
    "\n",
    "words_iter = jieba.cut(content)          ##使用jiejia进行分词操作\n",
    "print('/ '.join(words_iter))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n",
      "Loading model from cache /var/folders/jr/sc47kr2548z426dnw3xkrjqm0000gn/T/jieba.cache\n",
      "Loading model cost 0.792 seconds.\n",
      "Prefix dict has been built succesfully.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==========分词完成====================\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "def generate_word_file(input_char_file, output_word_file):        ##定义分词函数，并写入文件\n",
    "    with open(input_char_file, 'r') as f:\n",
    "        lines = f.readlines()\n",
    "    with open(output_word_file, 'w') as f:\n",
    "        for line in lines:\n",
    "            label, content = line.strip('\\r\\n').split('\\t')\n",
    "            words_iter = jieba.cut(content)\n",
    "            word_content = ''\n",
    "            for word in words_iter:\n",
    "                word = word.strip(' ')\n",
    "                if word != '':\n",
    "                    word_content += word + ' '\n",
    "            out_line = '%s\\t%s\\n' % (label, word_content.strip(' '))\n",
    "            f.write(out_line)\n",
    "\n",
    "generate_word_file(news_file, output_word_file)\n",
    "generate_word_file(test_file, output_word_test_file)\n",
    "print('==========分词完成====================')            ##需要的时间比较长"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "class Category:               ##分类topic\n",
    "    def __init__(self, category_file):\n",
    "        self._category_to_id = {}\n",
    "        with open(category_file, 'r') as f:\n",
    "            lines = f.readlines()\n",
    "        for line in lines:\n",
    "            category, idx = line.strip('\\r\\n').split('\\t')\n",
    "            idx = int(idx)\n",
    "            self._category_to_id[category] = idx\n",
    "    \n",
    "    def category_to_id(self, category):\n",
    "        return self._category_to_id[category]\n",
    "    \n",
    "    def size(self):\n",
    "        return len(self._category_to_id)\n",
    "\n",
    "category_file='cnews.category.txt'\n",
    "category_vocab = Category(category_file)\n",
    "print(category_vocab.size())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10353\n"
     ]
    }
   ],
   "source": [
    "\n",
    "##对分词后的数据进行词频统计并过滤，分配词ID\n",
    "\n",
    "def generate_feature_dict(train_file, feature_threshold=10):   \n",
    "    feature_dict = {}\n",
    "    with open(train_file, 'r') as f:\n",
    "        lines = f.readlines()\n",
    "    for line in lines:\n",
    "        label, content = line.strip('\\r\\n').split('\\t')\n",
    "        for word in content.split(' '):\n",
    "            if not word in feature_dict:\n",
    "                feature_dict.setdefault(word, 0)\n",
    "            feature_dict[word] += 1\n",
    "    filtered_feature_dict = {}\n",
    "    for feature_name in feature_dict:\n",
    "        if feature_dict[feature_name] < feature_threshold:\n",
    "            continue\n",
    "        if not feature_name in filtered_feature_dict:\n",
    "            filtered_feature_dict[feature_name] = len(filtered_feature_dict) + 1\n",
    "    return filtered_feature_dict\n",
    "        \n",
    "feature_dict = generate_feature_dict(output_word_file, feature_threshold=200)\n",
    "print(len(feature_dict))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==========构造词向量完成完成====================\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "def generate_feature_line(line, feature_dict, category_vocab):     ##对每一篇文章根据词id构造词向量。\n",
    "    label, content = line.strip('\\r\\n').split('\\t')\n",
    "    label_id = category_vocab.category_to_id(label)\n",
    "    feature_example = {}\n",
    "    for word in content.split(' '):\n",
    "        if not word in feature_dict:\n",
    "            continue\n",
    "        feature_id = feature_dict[word]\n",
    "        feature_example.setdefault(feature_id, 0)\n",
    "        feature_example[feature_id] += 1\n",
    "    feature_line = '%d' % label_id\n",
    "    sorted_feature_example = sorted(feature_example.items(), key=lambda d:d[0])\n",
    "    for item in sorted_feature_example:\n",
    "        feature_line += ' %d:%d' % item\n",
    "    return feature_line\n",
    "\n",
    "##循环没一篇文章，得到词向量化后的文件\n",
    "\n",
    "def convert_raw_to_feature(raw_file, feature_file, feature_dict, category_vocab):   \n",
    "    with open(raw_file, 'r') as f:\n",
    "        lines = f.readlines()\n",
    "    with open(feature_file, 'w') as f:\n",
    "        for line in lines:\n",
    "            feature_line = generate_feature_line(line, feature_dict, category_vocab)\n",
    "            f.write('%s\\n' % feature_line)\n",
    "            \n",
    "##测试数据运用相同的词ID表\n",
    "convert_raw_to_feature(output_word_file, feature_file, feature_dict, category_vocab)\n",
    "convert_raw_to_feature(output_word_test_file, feature_test_file, feature_dict, category_vocab)  \n",
    "print('==========构造词向量完成完成====================')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.0 {10155: 1.0, 6149: 2.0, 10246: 1.0, 5640: 1.0, 8713: 1.0, 8714: 1.0, 4107: 2.0, 12: 1.0, 5720: 1.0, 19: 2.0, 6164: 2.0, 6165: 1.0, 495: 2.0, 5656: 1.0, 7707: 1.0, 3101: 2.0, 6661: 3.0, 3104: 3.0, 5665: 1.0, 7714: 1.0, 9764: 1.0, 6181: 2.0, 7207: 1.0, 9259: 1.0, 6190: 1.0, 2286: 3.0, 3128: 1.0, 5177: 3.0, 570: 1.0, 3647: 1.0, 6208: 38.0, 4162: 1.0, 4167: 1.0, 4680: 4.0, 3146: 1.0, 3154: 1.0, 8291: 1.0, 5206: 1.0, 7255: 1.0, 6232: 1.0, 4187: 1.0, 9821: 1.0, 5218: 1.0, 4707: 1.0, 5186: 1.0, 3379: 1.0, 107: 21.0, 5740: 1.0, 2670: 3.0, 7794: 2.0, 6774: 1.0, 2476: 13.0, 1387: 1.0, 5253: 1.0, 5254: 1.0, 6797: 8.0, 5262: 1.0, 5265: 1.0, 1170: 1.0, 9647: 3.0, 3736: 2.0, 6511: 2.0, 8350: 1.0, 2208: 1.0, 1185: 1.0, 1187: 1.0, 2214: 7.0, 1706: 12.0, 8876: 4.0, 850: 2.0, 2222: 1.0, 6323: 1.0, 8885: 1.0, 4791: 3.0, 192: 1.0, 4128: 2.0, 5067: 1.0, 8900: 4.0, 10274: 1.0, 718: 1.0, 3796: 4.0, 9944: 2.0, 7385: 1.0, 6362: 2.0, 5853: 1.0, 4837: 3.0, 5352: 1.0, 7914: 1.0, 5870: 1.0, 2288: 4.0, 2289: 1.0, 3833: 1.0, 5882: 1.0, 9469: 1.0, 6398: 2.0, 7939: 1.0, 3844: 14.0, 8453: 1.0, 2310: 1.0, 9991: 1.0, 4878: 5.0, 2839: 1.0, 4377: 1.0, 6432: 2.0, 2340: 1.0, 10118: 2.0, 3878: 2.0, 5418: 3.0, 3463: 1.0, 3887: 1.0, 8497: 1.0, 818: 1.0, 2355: 1.0, 10035: 1.0, 6465: 2.0, 322: 1.0, 9027: 8.0, 9548: 1.0, 6991: 3.0, 2386: 4.0, 7508: 1.0, 2905: 1.0, 5469: 1.0, 10081: 1.0, 1891: 1.0, 4454: 2.0, 2919: 1.0, 4458: 2.0, 3435: 1.0, 2194: 1.0, 2927: 1.0, 9586: 1.0, 7030: 1.0, 10108: 1.0, 1770: 1.0, 2942: 1.0, 5525: 1.0, 6530: 1.0, 902: 2.0, 6977: 2.0, 5002: 1.0, 8075: 1.0, 909: 2.0, 7567: 1.0, 6034: 1.0, 6549: 4.0, 1942: 1.0, 1435: 1.0, 1438: 1.0, 2464: 2.0, 7586: 1.0, 10149: 1.0, 1959: 1.0, 9101: 1.0, 937: 2.0, 6727: 2.0, 6572: 1.0, 4525: 1.0, 6575: 2.0, 5192: 1.0, 1262: 2.0, 5022: 1.0, 7608: 2.0, 8123: 1.0, 6588: 4.0, 3007: 1.0, 1991: 4.0, 8650: 1.0, 8139: 1.0, 6606: 2.0, 3536: 1.0, 8659: 2.0, 3547: 3.0, 9696: 1.0, 10210: 3.0, 4603: 1.0, 6822: 1.0, 998: 1.0, 1618: 2.0, 5103: 1.0, 8688: 1.0, 5106: 1.0, 4598: 1.0, 3065: 2.0, 2555: 2.0, 4605: 1.0}\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "##生成svm训练数据\n",
    "train_label, train_value = svm_read_problem(feature_file)\n",
    "print(train_label[0],train_value[0])\n",
    "train_test_label, train_test_value = svm_read_problem(feature_test_file)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=======模型训练完成================\n"
     ]
    }
   ],
   "source": [
    "\n",
    "if(os.path.exists(model_filename)):                ##判断模型是否存在，存在直接读取\n",
    "    model=svm_load_model(model_filename)\n",
    "else:\n",
    "    model=svm_train(train_label,train_value,'-s 0 -c 5 -t 0 -g 0.5 -e 0.1')   ##模型训练\n",
    "    svm_save_model(model_filename,model)                    \n",
    "print(\"=======模型训练完成================\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy = 94.15% (9415/10000) (classification)\n",
      "(94.15, 0.6576, 0.9223329891919024)\n"
     ]
    }
   ],
   "source": [
    "\n",
    "##模型预测，并打印出精确度。\n",
    "p_labs, p_acc, p_vals =svm_predict(train_test_label, train_test_value, model)   \n",
    "print(p_acc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
